/*	COMP 6761 Assignment 1 Framework
	Created by Kaustubha Mendhurwar on 09/09/14.
	Copyright (c) 2014 Concordia University. All rights reserved.
*/

#include "Model.h"
#include "Renderer.h"
#include <iostream>
#include <memory>
#include <stdexcept>
#include <fstream>
#include <sstream>
#include "EventManager.h" 
#include "assimp/mesh.h"
#include "assimp/Importer.hpp"
#include <assimp/scene.h>          
#include <assimp/postprocess.h>     
using namespace std;


struct triangle {
	glm::vec3 vertices[3];

	bool has(glm::vec3 vector) const {
		return vector == vertices[0] || vector == vertices[1] || vector == vertices[2];
	}

	glm::vec3 opposite_point_of(glm::vec3 const & p0, glm::vec3 const & p1) const {
		for (auto & point : vertices) {
			if (point != p0 && point != p1) {
				return point;
			}
		}
		return glm::vec3(0, 0, 0);
	}

};

Model::Model(std::string const & filename) : mPosition(0.0f, 0.0f, 0.0f), mScaling(1.0f, 1.0f, 1.0f), mRotationAxis(1.0f, 1.0f, 0.0f), mRotationAngleInDegrees(0.0f)
{
	// Read our .obj file
	loadObj(filename);
	
}

Model::mesh::mesh(std::vector<glm::vec3> const mesh_vertices, std::vector<GLuint> const & mesh_vertexIndices, std::vector<glm::vec3> const & mesh_normals)
	: vertices(mesh_vertices), vertexIndices(mesh_vertexIndices), normals(mesh_normals), mVertexArrayID(0), mVertexArrayIndicesId(0), mVertexNormalId(0)
{
	// Create a vertex array
	glGenVertexArrays(1, &mVertexArrayID);
	glBindVertexArray(mVertexArrayID);


#ifndef OPENGL33
	glGenBuffers(1, &mVertexArrayIndicesId);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVertexArrayIndicesId);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, vertexIndices.size() * sizeof(glm::vec3), &vertexIndices[0], GL_STATIC_DRAW);

	glGenBuffers(1, &mVertexBufferID);
	glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferID);
	glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);

	glGenBuffers(1, &mVertexNormalId);
	glBindBuffer(GL_ARRAY_BUFFER, mVertexNormalId);
	glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_STATIC_DRAW);
#else
	glGenBuffers(1, &mVertexArrayIndicesId);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVertexArrayIndicesId);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, vertexIndices.size() * sizeof(glm::vec3), &vertexIndices[0], GL_STATIC_DRAW);
	glGenBuffers(1, &mVertexBufferID);
	glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferID);
	glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);

	glGenBuffers(1, &mVertexNormalId);
	glBindBuffer(GL_ARRAY_BUFFER, mVertexNormalId);
	glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_STATIC_DRAW);

#endif



	//std::cout << "Vertices size: " << vertices.size() << std::endl;
	//std::cout << "Primitives size: " << discretePrimitives.size() << std::endl;
	//std::cout << "Normals size: " << normals.size() << std::endl;
	//std::cout << "Adjacency array size: " << discretePrimitivesAdjacency.size() << std::endl;
}



Model::mesh::~mesh()
{
	// Free the GPU from the Vertex Buffer
	glDeleteBuffers(1, &mVertexBufferID);
	glDeleteBuffers(1, &mVertexNormalId);
	glDeleteVertexArrays(1, &mVertexArrayID);
}
namespace {
	void throwIfFailed(bool condition, std::string const & filename, unsigned int lineNumber, std::string const & optionalDetails = "") throw (std::runtime_error)
	{
		if (condition) {
			std::ostringstream oss;
			oss << "Error parsing file: " << filename << " on line: " << lineNumber << std::endl;
			if (optionalDetails.empty() == false) {
				oss << optionalDetails << std::endl;
			}
			throw std::runtime_error(oss.str());
		}
	}

	void throwIfFailed(std::istream const & inputStream, std::string const & filename, unsigned int lineNumber, std::string const & optionalDetails = "") throw (std::runtime_error)
	{
		throwIfFailed(inputStream.rdstate() & ios::failbit, filename, lineNumber, optionalDetails);
	}


}



void Model::loadObj(std::string const & path)
{
	std::cout << "Loading OBJ file" << path << std::endl;

	Assimp::Importer importer;
	aiScene const *scene = importer.ReadFile(path.c_str(), aiProcess_GenSmoothNormals);

	std::cout << "Meshes: " << scene->mNumMeshes << std::endl;

	for (unsigned int meshIdx = 0; meshIdx < scene->mNumMeshes; meshIdx++) {
		aiMesh const * mesh_in_file = scene->mMeshes[meshIdx];
		std::vector<glm::vec3> vertices;
		std::vector<GLuint> vertexIndices;
		std::vector<glm::vec3> normals;

		for (unsigned int i = 0 ; i < mesh_in_file->mNumFaces ; i++) {
			const aiFace& Face = mesh_in_file->mFaces[i];
			assert(Face.mNumIndices == 3);
			vertexIndices.push_back(Face.mIndices[0]);
			vertexIndices.push_back(Face.mIndices[1]);
			vertexIndices.push_back(Face.mIndices[2]);
		}

		for (unsigned int i = 0 ; i < mesh_in_file->mNumVertices ; i++) {
			aiVector3D const * pPos = &(mesh_in_file->mVertices[i]);
			aiVector3D const * pNormal = &(mesh_in_file->mNormals[i]);
			vertices.push_back(glm::vec3(pPos->x, pPos->y, pPos->z));
			normals.push_back(glm::vec3(pNormal->x, pNormal->y, pNormal->z));
		}

		meshes.push_back(std::unique_ptr<mesh>(new mesh(vertices, vertexIndices, normals)));
	}
}

void Model::Update(float dt)
{
}


void Model::mesh::draw() const {
	glBindVertexArray(mVertexArrayID);
	
	// 1st attribute buffer : vertex Positions
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferID);
	glVertexAttribPointer(	0,				// attribute. No particular reason for 0, but must match the layout in the shader.
							3,				// size -- why don't I need to change this for adjacency?? if I put 6 the application crashes
							GL_FLOAT,		// type
							GL_FALSE,		// normalized?
							0, // stride
							(void*)0        // array buffer offset
						);


	// 3rd attribute buffer : vertex Normals
	glEnableVertexAttribArray(1);
	// this should be mVertexNormalId, if can work with mVertexBufferID 
	// but it is a hack, and for objects that have the origin in their center and all faces outwards from the origin
	glBindBuffer(GL_ARRAY_BUFFER, mVertexNormalId); 
	glVertexAttribPointer(	1,
							3,
							GL_FLOAT,
							GL_FALSE,
							0,
							(void*)0
						);
//#ifndef OPENGL33
//#endif

#ifndef OPENGL33
//	glDrawArrays(GL_PATCHES, 0, discretePrimitives.size()); // 3 indices starting at 0 -> 1 triangle
glDrawElements(GL_PATCHES, vertexIndices.size(), GL_UNSIGNED_INT, 0);
#else
//	glDrawArrays(GL_TRIANGLES_ADJACENCY, 0, discretePrimitivesAdjacency.size());
glDrawElements(GL_TRIANGLES, vertexIndices.size(), GL_UNSIGNED_INT, 0);
#endif


	glDisableVertexAttribArray(1);
	glDisableVertexAttribArray(0);
    glBindVertexArray(0);
}

void Model::Draw()
{
	for (auto const & mesh : meshes) {
		mesh->draw();
	}
}

glm::mat4 Model::GetWorldMatrix() const
{
	glm::mat4 worldMatrix(1.0f);

	worldMatrix = glm::translate(worldMatrix, mPosition);
	worldMatrix = glm::rotate(worldMatrix, mRotationAngleInDegrees, mRotationAxis);
	worldMatrix = glm::scale(worldMatrix, mScaling * EventManager::zoomFactor);
	
	return worldMatrix;
}

void Model::SetPosition(glm::vec3 position)
{
	mPosition = position;
}

void Model::SetScaling(glm::vec3 scaling)
{
	mScaling = scaling;
}

void Model::SetRotation(glm::vec3 axis, float angleDegrees)
{
	mRotationAxis = axis;
	mRotationAngleInDegrees = angleDegrees;
}
